import { callChromeMethod, awaitTime, callContentMethodSync, getResourceSuffix } from "./com"
import { unsafeWindow } from "./unsafe-window";
import JSZip from "jszip"
import { BrowserUtils } from "./browser"
import { UserCache } from "./us-cache";
import CryptoJS from 'crypto-js'
import base64 from 'base64-js'

Reflect.set(window, 'unsafeWindow', unsafeWindow);

const HandlerMap: { [key: string]: any } = {};
const MENUS_PREFIX = 'menu';


export const GlobalMap: { [key: string]: any } = {}

export const EVENTS_PREFIX = 'script-dog-'

function callHandlerFunc(key: string, data: any) {
    if (typeof HandlerMap[key] === 'function') {
        return HandlerMap[key](data)
    }
}

function callGlobalFunc(key: string, data: any) {
    if (typeof GlobalMap[key] === 'function') {
        GlobalMap[key](data)
    }
}

function registerEls(els: NodeListOf<Element>) {
    for (const el of els) {
        if (!(el instanceof HTMLElement)) {
            continue
        }
        const func = el.getAttribute(`data-${EVENTS_PREFIX}func`)
        if (!func) {
            continue
        }
        let args: any = el.getAttribute(`data-${EVENTS_PREFIX}args`)
        if (el.tagName === 'INPUT') {
            const input = el as HTMLInputElement
            if (input.type === 'button') {
                input.onclick = () => {
                    callGlobalFunc(func, input)
                }
            } else {

                input.onchange = () => {
                    let val: any
                    if (!args) {
                        if (input.type === 'checkbox') {
                            val = input.checked
                        } else if (input.type === 'file') {
                            val = input.files
                        } else {
                            val = input.value
                        }
                    }
                    callGlobalFunc(func, args || val)
                }
            }

        } else if (el.tagName === 'TEXTAREA') {
            const area = el as HTMLTextAreaElement

            area.onchange = () => {
                callGlobalFunc(func, args || area.value)
            }
        } else if (el.tagName === 'SELECT') {
            const select = el as HTMLSelectElement
            select.onchange = () => {
                callGlobalFunc(func, args || select.value)
            }
        } else {
            el.onclick = () => {
                callGlobalFunc(func, args || el)
            }
        }
    }
}

/**
 * 注册事件以调用全局函数
 */
export async function registerEvents() {
    await awaitTime(700)
    const els = document.querySelectorAll(`[data-${EVENTS_PREFIX}func]`)
    registerEls(els)

    const shadows = document.querySelectorAll(`[data-shadow-root]`)
    for (const el of shadows) {
        const shadowRoot = el.shadowRoot
        if (!shadowRoot) {
            continue
        }
        const shadowEls = shadowRoot.querySelectorAll(`[data-${EVENTS_PREFIX}func]`)
        registerEls(shadowEls)
    }
}

// 适配GM_* GM.*
export function compatibleGM_x() {
    const win = window as any
    if (typeof win.Dog !== 'object') {
        return
    }
    delete win.Dog['compatibleGM_x'];
    const keys = Reflect.ownKeys(win.Dog);
    for (let i = 0; i < keys.length; i++) {
        const k = keys[i];
        if (typeof k !== 'string' || k === 'compatibleGM_x') {
            continue
        }
        const gk = `GM_${k}`;
        Reflect.set(win, gk, win.Dog[k]);
    }
    Reflect.set(win, 'GM', win.Dog);
}

// 初始化
export function init() {
    if (typeof chrome.runtime === 'undefined') {
        return
    }
    UserCache.init();
    const id = Dog.info.uuid;
    callChromeMethod('clearMenus', id);
    chrome.runtime?.onMessage.addListener((e: ChromeMessage) => {
        if (e.type.includes(MENUS_PREFIX)) {
            const id = e.type.replace(MENUS_PREFIX + '_', '');
            callHandlerFunc(id, e.data);
        }
    })
    console.log('script dog init')
}

/**
 * Dog API
 */

// cache
export function getValue(key: string, defaultValue?: string) {
    return UserCache.getValue(key, defaultValue)
}

export async function setValue(key: string, value: string | number) {
    return UserCache.setValue(key, value);
}

export async function deleteValue(key: string) {
    return UserCache.deleteValue(key)
}

export function listValues() {
    return UserCache.listValues()
}

//resourc
export function getResourceText(name: string) {
    const resources: ScriptResourceInfo[] = DogruntimeData['resources'] || [];
    const res = resources.find(s => {
        return s.key === name
    });
    if (!res) {
        return
    }
    const decoder = new TextDecoder();
    return decoder.decode(base64.toByteArray(res.data))
}

export function getResourceUrl(name: string) {
    const resources: ScriptResourceInfo[] = DogruntimeData['resources'] || [];
    const res = resources.find(s => {
        return s.key === name
    });
    if (!res) {
        return
    }

    const blob = new Blob([base64.toByteArray(res.data).buffer], { type:res.resourceType });
    return URL.createObjectURL(blob);

}

//tabs
export function openInTab(url: string, _options: any) {
    return callChromeMethod<string>('openInTab', { url })
}

//notification
export function notification(text: string, title?: string, image?: string, _onclick?: () => void) {
    return callChromeMethod<string>('notification', { text, title, image })
}

//command
export function registerMenuCommand(caption: string, commandFunc: () => void, _accessKey?: string) {
    const id = Dog.info.uuid;
    const cid = CryptoJS.MD5(`${id}_${caption}`).toString();
    const mid = `${id}_${cid}`;
    HandlerMap[mid] = commandFunc;
    callChromeMethod<string>('registerMenuCommand', { mid, caption });
    return mid
}

export function unregisterMenuCommand(id: string) {
    callChromeMethod('unregisterMenuCommand', id)
}

//clipboard
export function setClipboard(text: string) {
    return navigator.clipboard.writeText(text)
}

//xhr
export async function httpRequest(details: any) {
    const { onerror, onload, responseType } = details
    try {
        const res = await callChromeMethod<any>('xmlHttpRequest', details);
        if (responseType === 'arraybuffer') {
            res.response = base64.toByteArray(res.response)
        } else if (responseType === 'blob') {
            const buff = base64.toByteArray(res.response)
            res.response = new Blob([buff])
        } else if (responseType === 'json') {
            const buff = base64.toByteArray(res.response);
            const str = new TextDecoder().decode(buff);
            res.response = JSON.stringify(str);
        } else if (responseType === 'xml') {
            const buff = base64.toByteArray(res.response);
            const str = new TextDecoder().decode(buff);
            const parser = new DOMParser();
            res.response = parser.parseFromString(str, 'text/xml');
        }

        if (typeof onload === 'function') {
            onload(res)
        }
        return res
    } catch (error: any) {
        const response = {
            readyState: 0,
            responseText: '',
            status: 400,
            response: error.message || 'unknown error',
            statusText: 'error'
        }
        if (typeof onerror === 'function') {
            onerror(response)
        }
        return Promise.reject(response)
    }
}

export function xmlhttpRequest(details: any) {
    return httpRequest(details)
}

//css
export function addStyle(css: string) {
    return callContentMethodSync<HTMLStyleElement>('addStyle', { css })
}

//html tag|跨csp，可以注入外链js
export function addElement(tag: string, attributes: any) {
    return callContentMethodSync<HTMLElement>('addElement', { tag, attributes })
}

//下载文件
export function download(urlOrOptions: string | { [k: string]: any }, filename?: string) {
    if (typeof urlOrOptions === 'string') {
        return callChromeMethod('downloadFile', { url: urlOrOptions, filename });
    }
    const { url, name, onload } = urlOrOptions;
    callChromeMethod('downloadFile', { url, filename: name }).then(() => {
        if (typeof onload === 'function') {
            onload()
        }
    })
}

//批量下载
export async function downloadAll(ar: ResourceInfo[]) {
    if (!ar.length) {
        throw Error('No resources')
    }
    const zip = new JSZip();
    for (let i = 0; i < ar.length; i++) {
        try {
            const res = ar[i];
            const suffix = getResourceSuffix(res)
            const resp = await httpRequest({
                url: res.url,
                method: 'GET',
                responseType: 'arraybuffer',
            })
            const data = new Blob([resp.response])
            zip.file(res.name + suffix, data)
        } catch (error) {
            console.log(error)
        }
    }
    const blob = await zip.generateAsync({ type: "blob" })
    const file = new File([blob], "resources.zip", { type: "application/zip" })
    const url = URL.createObjectURL(file);
    const filename = Date.now() + '_resources.zip';
    BrowserUtils.download(url, filename)
}


export function hexToRgb(hex: string) {
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(hex);
    if (!result) {
        return null
    }
    return {
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16),
        a: result[4] ? parseInt(result[4], 16) : 255
    }
}

export function parseRgba(rgba: string) {
    const result = /^rgba?\((\d+),(\d+),(\d+),?([\d.]+)?\)$/.exec(rgba);
    if (!result) {
        return null
    }
    return {
        r: parseInt(result[1], 10),
        g: parseInt(result[2], 10),
        b: parseInt(result[3], 10),
        a: result[4] ? parseFloat(result[4]) : 255
    }
}

export function openFile(text = false) {
    return BrowserUtils.openFile(text)
}

export function desktopCapture(sources: string[]) {
    return callChromeMethod('desktopCapture', sources)
}

export function cancelDesktopCapture(desktopMediaRequestId: number) {
    return callChromeMethod('cancelDesktopCapture', desktopMediaRequestId)
}

export function loadImage(url: string) {
    return BrowserUtils.loadImage(url)
}

export function readFile(file: File, type = 'text') {
    return BrowserUtils.readFile(file, type)
}

export async function findResources(t?: ResourceInfoType) {
    return BrowserUtils.getResources(t)
}

export function registerTemplateFunction(funcName: string, func: any) {
    GlobalMap[funcName] = func
}

export function getTemplate(name: string, data: any = {}) {
    const tpl = Reflect.get(Dog, `get_${name}`);
    if (typeof tpl === 'function') {
        return tpl(data)
    } else {
        return 'template undefined'
    }
}